import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:common_utils/common_utils.dart';
import 'package:dart_sdk/channel_state.dart';
import 'package:dart_sdk/dio_util.dart';
import 'package:dart_sdk/protos/proto_message.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:web_socket_client/web_socket_client.dart';
import 'gateway_dio.dart';

const ping = 0;

const pong = 1;

const kicked = 2;

const login = 3;

const loginsuccess = 4;

const data = 5;

const subsuccess = 6;

const sub = 7;

class WebSocketIO extends ChangeNotifier {
  WebSocketIO(
      {required this.onMessageReceived,
      required this.onConnectionStatusChanged,
      required this.onKickoutMessageReceived});

  /// 句柄
  late WebSocket? channel;

  /// 地址
  late String? endPoint;

  /// 用户id
  late String? identity;

  ///
  late String endCode = "0";

  /// 消息回调
  final ValueChanged<MessageProtoBuf> onMessageReceived;

  /// 连接状态回调
  final ValueChanged<String> onConnectionStatusChanged;

  final ValueChanged<String> onKickoutMessageReceived;

  late List<ChannelState> channels = [];

  List<ChannelState> get getChannels => channels;

  late String selectedChannel = "";

  String get getSelectedChannel => selectedChannel;

  late List<MessageProtoBuf> messages = [];

  List<MessageProtoBuf> get getMessages => messages;

  late String _connectionStatus = 'Disconnected';

  String get connectionStatus => _connectionStatus;

  bool isKicked = false;

  late TimerUtil timerUtil = TimerUtil()
    ..mTotalTime = 1000 * 10
    ..setOnTimerTickCallback((millisUntilFinished) {
      onConnectionStatusChanged('ReConnecting');
      _connectionStatus = 'ReConnecting';
      if (millisUntilFinished == 0) {
        connect();
      }
      notifyListeners();
    });

  ///
  /// [uri] 地址
  /// [port] 端口
  /// [identity] 用户identity
  /// [useSsl] 是否使用ssl
  Future init(String endPoint, String identity, List<String> channels,
      String address) async {
    this.endPoint = endPoint;
    this.identity = identity;
    this.channels = channels.map((e) => ChannelState(channel: e)).toList();
    HoxinUtil.dio.options.baseUrl = address;
  }

  /// 连接
  /// [devicename] 设备名
  /// [appVersion] app版本
  /// [osVersion] 系统版本
  /// [packageName] 包名
  /// [deviceid] 设备id
  /// [language] 语言
  /// [channelName] 渠道名
  Future connect() async {
    if (endPoint == null || identity == null || channels.isEmpty) {
      throw IOException;
    }

    isKicked = false;

    channel = WebSocket(Uri.parse('$endPoint'),
        backoff: const ConstantBackoff(Duration(seconds: 10)),
        binaryType: 'arraybuffer');

    channel!.connection.listen((event) {
      if (event.runtimeType == Connected || event.runtimeType == Reconnected) {
        sendLoginMsg();
        onConnectionStatusChanged(
            event.runtimeType == Connected ? 'Connected' : 'Reconnected');
        _connectionStatus =
            event.runtimeType == Connected ? 'Connected' : 'Reconnected';
        notifyListeners();
      } else {
        onConnectionStatusChanged(event.runtimeType.toString());
      }
    });

    channel!.messages.listen((message) async {
      Uint8List datas = message;
      int type = datas[0] + datas[1] + datas[2] + datas[3];

      switch (type) {
        case ping:
          await sendPong();
          break;
        case loginsuccess:
          await sendBindChannelMsg();
          break;
        case kicked:
          isKicked = true;
          channel!.close();
          onKickoutMessageReceived("kicked");
          break;
        case data:
          int dataLength = datas[4] + datas[5] + datas[6] + datas[7];
          Uint8List data = datas.sublist(8, dataLength + 8);
          MessageProtoBuf msg = MessageProtoBuf.fromBuffer(data);
          await addMsgToChannel(msg);
          onMessageReceived(msg);
          break;
        case subsuccess:
          break;
      }
    }, onError: (error, StackTrace trace) {
      channel = null;
      onConnectionStatusChanged('Disconnected');
      _connectionStatus = 'Disconnected';
      if (!isKicked) {
        timerUtil.mTotalTime = 10000;
        timerUtil.startCountDown();
      }
      notifyListeners();
    }, onDone: () {
      channel = null;
      onConnectionStatusChanged('Disconnected');
      _connectionStatus = 'Disconnected';
      if (!isKicked) {
        timerUtil.mTotalTime = 10000;
        timerUtil.startCountDown();
      }
      notifyListeners();
    }, cancelOnError: true);
  }

  Future addMsgToChannel(MessageProtoBuf msg) async {
    print("receive msg:${msg.toProto3Json()}");
    if (channels.any((channel) => channel.channel == msg.channel)) {
      channels.firstWhere((channel) => channel.channel == msg.channel).data =
          msg.data;
      channels
          .firstWhere((channel) => channel.channel == msg.channel)
          .timestamp = msg.timestamp;
      channels.firstWhere((channel) => channel.channel == msg.channel).id =
          msg.id;
      ChannelState channelState =
          channels.firstWhere((channel) => channel.channel == msg.channel);
      channels.remove(channelState);
      channels.insert(0, channelState);
    } else {
      ChannelState channelState = ChannelState(
          channel: msg.channel,
          data: msg.data,
          id: msg.id,
          timestamp: msg.timestamp);
      channels.insert(0, channelState);
    }
    if (msg.channel == selectedChannel) {
      messages.insert(0, msg);
    }
    notifyListeners();
  }

  Future selectChannel(String channel) async {
    selectedChannel = channel;
    messages.clear();
    notifyListeners();
  }

  //发送登录消息
  Future sendLoginMsg() async {
    var body = utf8.encode(identity!);
    int length = body.length;
    var data = Uint8List(length + 8);
    data[0] = 0;
    data[1] = 0;
    data[2] = 0;
    data[3] = login;

    data[7] = ((length) & 0xff);
    data[6] = ((data.length >> 8) & 0xff);
    data[5] = ((data.length >> 16) & 0xff);
    data[4] = ((data.length >> 24) & 0xff);

    data.setRange(8, length + 8, body);
    channel!.send(data);
    onConnectionStatusChanged('Connected');
    _connectionStatus = 'Connected';
    notifyListeners();
  }

  Future sendBindChannelMsg() async {
    var body = utf8.encode(channels.map((e) => e.channel!).join(','));
    int length = body.length;
    var data = Uint8List(length + 8);
    data[0] = 0;
    data[1] = 0;
    data[2] = 0;
    data[3] = sub;

    data[7] = ((length) & 0xff);
    data[6] = ((data.length >> 8) & 0xff);
    data[5] = ((data.length >> 16) & 0xff);
    data[4] = ((data.length >> 24) & 0xff);

    data.setRange(8, length + 8, body);
    channel!.send(data);
    onConnectionStatusChanged('SubSuccess');
    _connectionStatus = 'SubSuccess';
    notifyListeners();
  }

//发送PONG响应
  Future sendPong() async {
    var pONGBODY = Uint8List(8);
    pONGBODY[0] = 0;
    pONGBODY[1] = 0;
    pONGBODY[2] = 0;
    pONGBODY[3] = pong;
    pONGBODY[4] = 0;
    pONGBODY[5] = 0;
    pONGBODY[6] = 0;
    pONGBODY[7] = 0;
    channel!.send(pONGBODY);
  }
}
